﻿using Lidgren.Network;
using System.Threading;
using System;
using System.Xml;
using Paypayes.Aelda.Core.SimulationEvents;
using System.Timers;
using System.Collections.Generic;
using System.Reflection;

namespace Paypayes.Aelda.Core.Server
{
    public class ServerSimulation : BaseSimulation, IDisposable
    {
        private NetServer _netServer;
        private volatile Boolean _stopCommunication = false;
        private Thread _communicationThread = null;

        private SobManager _manager = new SobManager();
        private IEventPacker _eventPacker = null;
        private Dictionary<EventType, Func<NetConnection, SimulationEvent, Boolean>> _eventHandler = null;

        delegate void EventProcess(NetConnection sender, SimulationEvent evt);
        
        public ServerSimulation()
            : base()
        {
            // set up network
            NetPeerConfiguration config = new NetPeerConfiguration("aelda");
            config.MaximumConnections = 100;
            config.Port = 21420;
            _netServer = new NetServer(config);

            // auto generate event pack/unpack
            _eventPacker = EventPackerGenerator.Generate();

            // fillup event handler
            InitNetworkEventsHandler();

//            var actions = new Dictionary<int, Action>
//{
//  {1, () => Console.WriteLine("One!")},
//  {2, () => Console.WriteLine("Two!")}
//}

            ThreadStart threadDelegate = new ThreadStart(CommunicationThread);
            _communicationThread = new Thread(threadDelegate);
            _communicationThread.Priority = ThreadPriority.BelowNormal;
            _communicationThread.Name = "network";
        }

        private void InitNetworkEventsHandler()
        {
            _eventHandler = new Dictionary<EventType, Func<NetConnection, SimulationEvent, Boolean>>();
            string ns = "Paypayes.Aelda.Core.SimulationEvents";

            foreach (String e in Enum.GetNames(typeof(EventType)))
            {
                EventType eventType;
                Boolean ok = Enum.TryParse<EventType>(e, out eventType);
                String typeName = String.Format("{0}.{1}, Paypayes.Aelda.Core", ns, e);
                System.Diagnostics.Debug.Write(String.Format("Looking for type: {0}...", typeName));

                String s = String.Format("Process{0}", e);

                MethodInfo method = this.GetType()
            .GetMethod(s,
                       BindingFlags.NonPublic
                       | BindingFlags.Instance
                       | BindingFlags.FlattenHierarchy);

                if (method != null)
                {
                    Func<NetConnection, SimulationEvent, Boolean> converted =
                        (Func<NetConnection, SimulationEvent, Boolean>)Delegate.CreateDelegate
             (typeof(Func<NetConnection, SimulationEvent, Boolean>), this, s);

                    Type t = typeof(ServerSimulation);
                    _eventHandler[eventType] = converted;
                }
                else
                {
                    System.Diagnostics.Trace.WriteLine(String.Format("WARNING: handler {0} not found", s));
                }
            }
        }

        public void CommunicationThread()
        {
            _netServer.Start();

            while (!_stopCommunication)
            {
                NetIncomingMessage im;
                while ((im = _netServer.ReadMessage()) != null)
                {
                    // handle incoming message
                    switch (im.MessageType)
                    {
                        case NetIncomingMessageType.DebugMessage:
                        case NetIncomingMessageType.ErrorMessage:
                        case NetIncomingMessageType.WarningMessage:
                        case NetIncomingMessageType.VerboseDebugMessage:
                            string text = im.ReadString();
                            break;
                        case NetIncomingMessageType.StatusChanged:
                            NetConnectionStatus status = (NetConnectionStatus)im.ReadByte();
                            string reason = im.ReadString();
                            //Output(NetUtility.ToHexString(im.SenderConnection.RemoteUniqueIdentifier) + " " + status + ": " + reason);

                            //UpdateConnectionsList();
                            break;
                        case NetIncomingMessageType.Data:
                            SimulationEvent evt = _eventPacker.Unpack(im);

                            // incoming chat message from a client
                            //string chat = im.ReadString();
                            switch (evt.EventType)
                            {
                                case EventType.Login:
                                    ProcessLogin(im.SenderConnection, (Login)evt);
                                    break;
                                case EventType.ActorMoved:
                                    {
                                        Console.WriteLine("actor moved (id: {0}) to {2} at {1}", evt.SourceId, ((ActorMoved)evt).StartTime, ((ActorMoved)evt).Direction);

                                        Object om = _netServer.CreateMessage();

                                        ActorMovedAck ama = new ActorMovedAck()
                                        {
                                            Accepted = true
                                        };
                                        ama.SourceId = 0;
                                        ama.TargetId = evt.SourceId;
                                        _eventPacker.Pack(ama, ref om);
                                        _netServer.SendMessage((NetOutgoingMessage)om, im.SenderConnection, NetDeliveryMethod.ReliableOrdered, 0);
                                        Console.WriteLine("Sending 'UserAttached'");
                                        _netServer.FlushSendQueue();
                                    }
                                    break;
                                case EventType.ActorStoppedMoving:
                                    Console.WriteLine("l'acteur (id: {0}) a arrêté de bouger à {1}", evt.SourceId, ((ActorStoppedMoving)evt).Time);
                                    break;
                                default:
                                    Console.WriteLine("rien pige");
                                    break;
                            }
                            //Output("Broadcasting '" + chat + "'");

                            // broadcast this to all connections, except sender
                            //List<NetConnection> all = netServer.Connections; // get copy
                            //all.Remove(im.SenderConnection);

                            //if (all.Count > 0)
                            //{
                            //    NetOutgoingMessage om = netServer.CreateMessage();
                            //    om.Write(NetUtility.ToHexString(im.SenderConnection.RemoteUniqueIdentifier) + " said: " + chat);
                            //    netServer.SendMessage(om, all, NetDeliveryMethod.ReliableOrdered, 0);
                            //}
                            break;
                        default:
                            //Output("Unhandled type: " + im.MessageType + " " + im.LengthBytes + " bytes " + im.DeliveryMethod + "|" + im.SequenceChannel);
                            break;
                    }
                    _netServer.Recycle(im);
                }
                Thread.Sleep(100);
            }
            _netServer.Shutdown("game over");
        }

        private Boolean ProcessLogin(NetConnection sender, SimulationEvent evt)
        {
            Console.WriteLine("login requested: " + ((Login)evt).User + "/" + ((Login)evt).Password);

            if ((((Login)evt).User != "user") || (((Login)evt).Password != "password"))
            {
                Object om = _netServer.CreateMessage();
                LoginResult resultEvt = new LoginResult()
                {
                    Granted = false
                };
                _eventPacker.Pack(resultEvt, ref om);
                _netServer.SendMessage((NetOutgoingMessage)om, sender, NetDeliveryMethod.Unreliable, 0);
            }
            else
            {
                Object om = _netServer.CreateMessage();
                LoginResult resultEvt = new LoginResult()
                {
                    Granted = true
                };
                _eventPacker.Pack(resultEvt, ref om);
                _netServer.SendMessage((NetOutgoingMessage)om, sender, NetDeliveryMethod.ReliableOrdered, 0);

                User user = new User();
                user.Login = ((Login)evt).User;
                user.Password = ((Login)evt).Password;
                user.NetConnection = sender;
                Actor actor = (Actor)SobFactory.Create(SobType.Actor);
                actor.Name = "ratata";
                //actor.Restore();

                AttachUser(user, actor);

                _manager.Add(actor);
                _areaManager.LookupById(1).Contents.Add(actor.Id, actor);
                actor.OwnerId = _areaManager.LookupById(1).Id;
                actor.Properties.Add("X", 5);
                actor.Properties.Add("Y", 5);

                om = _netServer.CreateMessage();
                UserAttached lev = new UserAttached()
                {
                    Success = true,
                    AreaId = 1,
                    X = 50,
                    Y = 25
                };
                lev.SourceId = actor.Id;
                _eventPacker.Pack(lev, ref om);
                _netServer.SendMessage((NetOutgoingMessage)om, sender, NetDeliveryMethod.ReliableOrdered, 0);
                Console.WriteLine("Sending 'UserAttached'");
            }
            _netServer.FlushSendQueue();
            return true;
        }

        private Boolean ActorMoved(NetConnection sender, SimulationEvent evt)
        {
            return true; 
        }

        private Boolean ActorMovedAck(NetConnection sender, SimulationEvent evt)
        {
            return true; 
        }

        private Boolean ActorStoppedMoving(NetConnection sender, SimulationEvent evt)
        {
            return true;
        }

        private Boolean ClientReadyToPlay(NetConnection sender, SimulationEvent evt)
        {
            return true;
        }
        
        public override void AttachUser()
        {
        }



        public void AttachUser(User who, Actor to)
        {

            if (to.CurrentControlState != null)
            {
                to.CurrentControlState.RequestAction();
            }
            else
            {
                // l'actor n'a aucun controle donc de toute façon, on force la nouvelle
                to.CurrentControlState = new UserControlState();
            }

            base.AttachUser();
        }


        public override void DetachUser()
        {
            base.DetachUser();
        }

        public void ReceiveActionRequest()
        {
            //_actorManager.LookupById
        }

        public void SendSimulationEvent()
        {
        }

        public override void ShutDown()
        {
            _stopCommunication = true;
            _communicationThread.Join();

            XmlElement e = _areaManager.Store();

            XmlTextReader reader = new XmlTextReader(@"save.xml");
            XmlDocument doc = new XmlDocument();
            doc.Load(reader);
            reader.Close();

            //Select the cd node with the matching title
            XmlNode oldCd;
            XmlElement root = doc.DocumentElement;
            oldCd = root.SelectSingleNode("/areas");

            XmlElement newCd = doc.CreateElement("areas");
            newCd.InnerXml = e.InnerXml;

            if (oldCd != null)
            {
                root.ReplaceChild(newCd, oldCd);
            }
            else
            {
                root.AppendChild(newCd);
            }

            //save the output to a file
            doc.Save(@"c:\temp\save.xml");

            _actorManager.Store();

            base.ShutDown();
        }

        public override void StartUp()
        {
            base.StartUp();

            Area area = new Area(1);
            //area.Restore();
            _areaManager.Add(area);

            _communicationThread.Start();
        }

        public override void Tick()
        {
            base.Tick();
        }


        public void Dispose()
        {

        }
    }
}
